// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/service_worker/service_worker_url_request_job.h"

#include <stddef.h>
#include <stdint.h>

#include <map>
#include <string>
#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/files/file_util.h"
#include "base/guid.h"
#include "base/location.h"
#include "base/numerics/safe_conversions.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/task_runner.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "content/browser/resource_context_impl.h"
#include "content/browser/service_worker/embedded_worker_instance.h"
#include "content/browser/service_worker/service_worker_blob_reader.h"
#include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
#include "content/browser/service_worker/service_worker_provider_host.h"
#include "content/browser/service_worker/service_worker_response_info.h"
#include "content/browser/service_worker/service_worker_stream_reader.h"
#include "content/common/resource_request_body_impl.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/blob_handle.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/common/referrer.h"
#include "net/base/net_errors.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
#include "net/http/http_util.h"
#include "net/log/net_log.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_with_source.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "ui/base/page_transition_types.h"

namespace content {

namespace {

    net::NetLogEventType RequestJobResultToNetEventType(
        ServiceWorkerMetrics::URLRequestJobResult result)
    {
        using n = net::NetLogEventType;
        using m = ServiceWorkerMetrics;
        switch (result) {
        case m::REQUEST_JOB_FALLBACK_RESPONSE:
            return n::SERVICE_WORKER_FALLBACK_RESPONSE;
        case m::REQUEST_JOB_FALLBACK_FOR_CORS:
            return n::SERVICE_WORKER_FALLBACK_FOR_CORS;
        case m::REQUEST_JOB_HEADERS_ONLY_RESPONSE:
            return n::SERVICE_WORKER_HEADERS_ONLY_RESPONSE;
        case m::REQUEST_JOB_STREAM_RESPONSE:
            return n::SERVICE_WORKER_STREAM_RESPONSE;
        case m::REQUEST_JOB_BLOB_RESPONSE:
            return n::SERVICE_WORKER_BLOB_RESPONSE;
        case m::REQUEST_JOB_ERROR_RESPONSE_STATUS_ZERO:
            return n::SERVICE_WORKER_ERROR_RESPONSE_STATUS_ZERO;
        case m::REQUEST_JOB_ERROR_BAD_BLOB:
            return n::SERVICE_WORKER_ERROR_BAD_BLOB;
        case m::REQUEST_JOB_ERROR_NO_PROVIDER_HOST:
            return n::SERVICE_WORKER_ERROR_NO_PROVIDER_HOST;
        case m::REQUEST_JOB_ERROR_NO_ACTIVE_VERSION:
            return n::SERVICE_WORKER_ERROR_NO_ACTIVE_VERSION;
        case m::REQUEST_JOB_ERROR_FETCH_EVENT_DISPATCH:
            return n::SERVICE_WORKER_ERROR_FETCH_EVENT_DISPATCH;
        case m::REQUEST_JOB_ERROR_BLOB_READ:
            return n::SERVICE_WORKER_ERROR_BLOB_READ;
        case m::REQUEST_JOB_ERROR_STREAM_ABORTED:
            return n::SERVICE_WORKER_ERROR_STREAM_ABORTED;
        case m::REQUEST_JOB_ERROR_KILLED:
            return n::SERVICE_WORKER_ERROR_KILLED;
        case m::REQUEST_JOB_ERROR_KILLED_WITH_BLOB:
            return n::SERVICE_WORKER_ERROR_KILLED_WITH_BLOB;
        case m::REQUEST_JOB_ERROR_KILLED_WITH_STREAM:
            return n::SERVICE_WORKER_ERROR_KILLED_WITH_STREAM;
        case m::REQUEST_JOB_ERROR_BAD_DELEGATE:
            return n::SERVICE_WORKER_ERROR_BAD_DELEGATE;
        case m::REQUEST_JOB_ERROR_REQUEST_BODY_BLOB_FAILED:
            return n::SERVICE_WORKER_ERROR_REQUEST_BODY_BLOB_FAILED;
        // We can't log if there's no request; fallthrough.
        case m::REQUEST_JOB_ERROR_NO_REQUEST:
        // Obsolete types; fallthrough.
        case m::REQUEST_JOB_ERROR_DESTROYED:
        case m::REQUEST_JOB_ERROR_DESTROYED_WITH_BLOB:
        case m::REQUEST_JOB_ERROR_DESTROYED_WITH_STREAM:
        // Invalid type.
        case m::NUM_REQUEST_JOB_RESULT_TYPES:
            NOTREACHED() << result;
        }
        NOTREACHED() << result;
        return n::FAILED;
    }

    std::vector<int64_t> GetFileSizesOnBlockingPool(
        std::vector<base::FilePath> file_paths)
    {
        std::vector<int64_t> sizes;
        sizes.reserve(file_paths.size());
        for (const base::FilePath& path : file_paths) {
            base::File::Info file_info;
            if (!base::GetFileInfo(path, &file_info) || file_info.is_directory)
                return std::vector<int64_t>();
            sizes.push_back(file_info.size);
        }
        return sizes;
    }

} // namespace

// Sets the size on each DataElement in the request body that is a file with
// unknown size. This ensures ServiceWorkerURLRequestJob::CreateRequestBodyBlob
// can successfuly create a blob from the data elements, as files with unknown
// sizes are not supported by the blob storage system.
class ServiceWorkerURLRequestJob::FileSizeResolver {
public:
    explicit FileSizeResolver(ServiceWorkerURLRequestJob* owner)
        : owner_(owner)
        , weak_factory_(this)
    {
        TRACE_EVENT_ASYNC_BEGIN1("ServiceWorker", "FileSizeResolver", this, "URL",
            owner_->request()->url().spec());
        owner_->request()->net_log().BeginEvent(
            net::NetLogEventType::SERVICE_WORKER_WAITING_FOR_REQUEST_BODY_FILES);
    }

    ~FileSizeResolver()
    {
        owner_->request()->net_log().EndEvent(
            net::NetLogEventType::SERVICE_WORKER_WAITING_FOR_REQUEST_BODY_FILES,
            net::NetLog::BoolCallback("success", phase_ == Phase::SUCCESS));
        TRACE_EVENT_ASYNC_END1("ServiceWorker", "FileSizeResolver", this, "Success",
            phase_ == Phase::SUCCESS);
    }

    void Resolve(base::TaskRunner* file_runner,
        const base::Callback<void(bool)>& callback)
    {
        DCHECK_EQ(static_cast<int>(Phase::INITIAL), static_cast<int>(phase_));
        DCHECK(file_elements_.empty());
        phase_ = Phase::WAITING;
        body_ = owner_->body_;
        callback_ = callback;

        std::vector<base::FilePath> file_paths;
        for (ResourceRequestBodyImpl::Element& element :
            *body_->elements_mutable()) {
            if (element.type() == ResourceRequestBodyImpl::Element::TYPE_FILE && element.length() == ResourceRequestBodyImpl::Element::kUnknownSize) {
                file_elements_.push_back(&element);
                file_paths.push_back(element.path());
            }
        }
        if (file_elements_.empty()) {
            Complete(true);
            return;
        }

        PostTaskAndReplyWithResult(
            file_runner, FROM_HERE,
            base::Bind(&GetFileSizesOnBlockingPool, base::Passed(&file_paths)),
            base::Bind(
                &ServiceWorkerURLRequestJob::FileSizeResolver::OnFileSizesResolved,
                weak_factory_.GetWeakPtr()));
    }

private:
    enum class Phase { INITIAL,
        WAITING,
        SUCCESS,
        FAIL };

    void OnFileSizesResolved(std::vector<int64_t> sizes)
    {
        bool success = !sizes.empty();
        if (success) {
            DCHECK_EQ(sizes.size(), file_elements_.size());
            size_t num_elements = file_elements_.size();
            for (size_t i = 0; i < num_elements; i++) {
                ResourceRequestBodyImpl::Element* element = file_elements_[i];
                element->SetToFilePathRange(element->path(), element->offset(),
                    base::checked_cast<uint64_t>(sizes[i]),
                    element->expected_modification_time());
            }
            file_elements_.clear();
        }
        Complete(success);
    }

    void Complete(bool success)
    {
        DCHECK_EQ(static_cast<int>(Phase::WAITING), static_cast<int>(phase_));
        phase_ = success ? Phase::SUCCESS : Phase::FAIL;
        // Destroys |this|, so we use a copy.
        base::ResetAndReturn(&callback_).Run(success);
    }

    // Owns and must outlive |this|.
    ServiceWorkerURLRequestJob* owner_;

    scoped_refptr<ResourceRequestBodyImpl> body_;
    std::vector<ResourceRequestBodyImpl::Element*> file_elements_;
    base::Callback<void(bool)> callback_;
    Phase phase_ = Phase::INITIAL;
    base::WeakPtrFactory<FileSizeResolver> weak_factory_;

    DISALLOW_COPY_AND_ASSIGN(FileSizeResolver);
};

bool ServiceWorkerURLRequestJob::Delegate::RequestStillValid(
    ServiceWorkerMetrics::URLRequestJobResult* result)
{
    return true;
}

ServiceWorkerURLRequestJob::ServiceWorkerURLRequestJob(
    net::URLRequest* request,
    net::NetworkDelegate* network_delegate,
    const std::string& client_id,
    base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
    const ResourceContext* resource_context,
    FetchRequestMode request_mode,
    FetchCredentialsMode credentials_mode,
    FetchRedirectMode redirect_mode,
    ResourceType resource_type,
    RequestContextType request_context_type,
    RequestContextFrameType frame_type,
    scoped_refptr<ResourceRequestBodyImpl> body,
    ServiceWorkerFetchType fetch_type,
    const base::Optional<base::TimeDelta>& timeout,
    Delegate* delegate)
    : net::URLRequestJob(request, network_delegate)
    , delegate_(delegate)
    , response_type_(NOT_DETERMINED)
    , is_started_(false)
    , service_worker_response_type_(blink::WebServiceWorkerResponseTypeDefault)
    , client_id_(client_id)
    , blob_storage_context_(blob_storage_context)
    , resource_context_(resource_context)
    , request_mode_(request_mode)
    , credentials_mode_(credentials_mode)
    , redirect_mode_(redirect_mode)
    , resource_type_(resource_type)
    , request_context_type_(request_context_type)
    , frame_type_(frame_type)
    , fall_back_required_(false)
    , body_(body)
    , fetch_type_(fetch_type)
    , timeout_(timeout)
    , weak_factory_(this)
{
    DCHECK(delegate_) << "ServiceWorkerURLRequestJob requires a delegate";
}

ServiceWorkerURLRequestJob::~ServiceWorkerURLRequestJob()
{
    stream_reader_.reset();
    file_size_resolver_.reset();

    if (!ShouldRecordResult())
        return;
    ServiceWorkerMetrics::URLRequestJobResult result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_KILLED;
    if (response_body_type_ == STREAM)
        result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_KILLED_WITH_STREAM;
    else if (response_body_type_ == BLOB)
        result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_KILLED_WITH_BLOB;
    RecordResult(result);
}

void ServiceWorkerURLRequestJob::FallbackToNetwork()
{
    DCHECK_EQ(NOT_DETERMINED, response_type_);
    DCHECK(!IsFallbackToRendererNeeded());
    response_type_ = FALLBACK_TO_NETWORK;
    MaybeStartRequest();
}

void ServiceWorkerURLRequestJob::FallbackToNetworkOrRenderer()
{
    DCHECK_EQ(NOT_DETERMINED, response_type_);
    DCHECK_NE(ServiceWorkerFetchType::FOREIGN_FETCH, fetch_type_);
    if (IsFallbackToRendererNeeded()) {
        response_type_ = FALLBACK_TO_RENDERER;
    } else {
        response_type_ = FALLBACK_TO_NETWORK;
    }
    MaybeStartRequest();
}

void ServiceWorkerURLRequestJob::ForwardToServiceWorker()
{
    DCHECK_EQ(NOT_DETERMINED, response_type_);
    response_type_ = FORWARD_TO_SERVICE_WORKER;
    MaybeStartRequest();
}

void ServiceWorkerURLRequestJob::FailDueToLostController()
{
    DCHECK_EQ(NOT_DETERMINED, response_type_);
    response_type_ = FAIL_DUE_TO_LOST_CONTROLLER;
    MaybeStartRequest();
}

void ServiceWorkerURLRequestJob::Start()
{
    is_started_ = true;
    MaybeStartRequest();
}

void ServiceWorkerURLRequestJob::Kill()
{
    net::URLRequestJob::Kill();
    stream_reader_.reset();
    fetch_dispatcher_.reset();
    blob_reader_.reset();
    weak_factory_.InvalidateWeakPtrs();
}

net::LoadState ServiceWorkerURLRequestJob::GetLoadState() const
{
    // TODO(kinuko): refine this for better debug.
    return net::URLRequestJob::GetLoadState();
}

bool ServiceWorkerURLRequestJob::GetCharset(std::string* charset)
{
    if (!http_info())
        return false;
    return http_info()->headers->GetCharset(charset);
}

bool ServiceWorkerURLRequestJob::GetMimeType(std::string* mime_type) const
{
    if (!http_info())
        return false;
    return http_info()->headers->GetMimeType(mime_type);
}

void ServiceWorkerURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info)
{
    if (!http_info())
        return;
    const base::Time request_time = info->request_time;
    *info = *http_info();
    info->request_time = request_time;
    info->response_time = response_time_;
}

void ServiceWorkerURLRequestJob::GetLoadTimingInfo(
    net::LoadTimingInfo* load_timing_info) const
{
    *load_timing_info = load_timing_info_;
}

int ServiceWorkerURLRequestJob::GetResponseCode() const
{
    if (!http_info())
        return -1;
    return http_info()->headers->response_code();
}

void ServiceWorkerURLRequestJob::SetExtraRequestHeaders(
    const net::HttpRequestHeaders& headers)
{
    std::string range_header;
    std::vector<net::HttpByteRange> ranges;
    if (!headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header) || !net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
        return;
    }

    // We don't support multiple range requests in one single URL request.
    if (ranges.size() == 1U)
        byte_range_ = ranges[0];
}

int ServiceWorkerURLRequestJob::ReadRawData(net::IOBuffer* buf, int buf_size)
{
    DCHECK(buf);
    DCHECK_GE(buf_size, 0);

    if (stream_reader_)
        return stream_reader_->ReadRawData(buf, buf_size);
    if (blob_reader_)
        return blob_reader_->ReadRawData(buf, buf_size);

    return 0;
}

void ServiceWorkerURLRequestJob::OnResponseStarted()
{
    if (response_time_.is_null())
        response_time_ = base::Time::Now();
    CommitResponseHeader();
}

void ServiceWorkerURLRequestJob::OnReadRawDataComplete(int bytes_read)
{
    ReadRawDataComplete(bytes_read);
}

void ServiceWorkerURLRequestJob::RecordResult(
    ServiceWorkerMetrics::URLRequestJobResult result)
{
    // It violates style guidelines to handle a NOTREACHED() failure but if there
    // is a bug don't let it corrupt UMA results by double-counting.
    if (!ShouldRecordResult()) {
        NOTREACHED();
        return;
    }
    did_record_result_ = true;
    ServiceWorkerMetrics::RecordURLRequestJobResult(IsMainResourceLoad(), result);
    request()->net_log().AddEvent(RequestJobResultToNetEventType(result));
}

base::WeakPtr<ServiceWorkerURLRequestJob>
ServiceWorkerURLRequestJob::GetWeakPtr()
{
    return weak_factory_.GetWeakPtr();
}

const net::HttpResponseInfo* ServiceWorkerURLRequestJob::http_info() const
{
    if (!http_response_info_)
        return nullptr;
    if (range_response_info_)
        return range_response_info_.get();
    return http_response_info_.get();
}

void ServiceWorkerURLRequestJob::MaybeStartRequest()
{
    if (is_started_ && response_type_ != NOT_DETERMINED) {
        // Start asynchronously.
        base::ThreadTaskRunnerHandle::Get()->PostTask(
            FROM_HERE, base::Bind(&ServiceWorkerURLRequestJob::StartRequest, weak_factory_.GetWeakPtr()));
    }
}

void ServiceWorkerURLRequestJob::StartRequest()
{
    request()->net_log().AddEvent(
        net::NetLogEventType::SERVICE_WORKER_START_REQUEST);

    switch (response_type_) {
    case NOT_DETERMINED:
        NOTREACHED();
        return;

    case FAIL_DUE_TO_LOST_CONTROLLER:
        request()->net_log().AddEvent(
            net::NetLogEventType::SERVICE_WORKER_ERROR_NO_ACTIVE_VERSION);
        NotifyStartError(net::URLRequestStatus::FromError(net::ERR_FAILED));
        return;

    case FALLBACK_TO_NETWORK:
        FinalizeFallbackToNetwork();
        return;

    case FALLBACK_TO_RENDERER:
        FinalizeFallbackToRenderer();
        return;

    case FORWARD_TO_SERVICE_WORKER:
        if (HasRequestBody()) {
            DCHECK(!file_size_resolver_);
            file_size_resolver_.reset(new FileSizeResolver(this));
            file_size_resolver_->Resolve(
                BrowserThread::GetBlockingPool(),
                base::Bind(
                    &ServiceWorkerURLRequestJob::RequestBodyFileSizesResolved,
                    GetWeakPtr()));
            return;
        }

        RequestBodyFileSizesResolved(true);
        return;
    }

    NOTREACHED();
}

std::unique_ptr<ServiceWorkerFetchRequest>
ServiceWorkerURLRequestJob::CreateFetchRequest()
{
    std::string blob_uuid;
    uint64_t blob_size = 0;
    if (HasRequestBody())
        CreateRequestBodyBlob(&blob_uuid, &blob_size);
    std::unique_ptr<ServiceWorkerFetchRequest> request(
        new ServiceWorkerFetchRequest());
    request->mode = request_mode_;
    request->is_main_resource_load = IsMainResourceLoad();
    request->request_context_type = request_context_type_;
    request->frame_type = frame_type_;
    request->url = request_->url();
    request->method = request_->method();
    const net::HttpRequestHeaders& headers = request_->extra_request_headers();
    for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) {
        if (ServiceWorkerContext::IsExcludedHeaderNameForFetchEvent(it.name()))
            continue;
        request->headers[it.name()] = it.value();
    }
    request->blob_uuid = blob_uuid;
    request->blob_size = blob_size;
    request->credentials_mode = credentials_mode_;
    request->redirect_mode = redirect_mode_;
    request->client_id = client_id_;
    const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_);
    if (info) {
        request->is_reload = ui::PageTransitionCoreTypeIs(
            info->GetPageTransition(), ui::PAGE_TRANSITION_RELOAD);
        request->referrer = Referrer(GURL(request_->referrer()), info->GetReferrerPolicy());
    } else {
        CHECK(
            request_->referrer_policy() == net::URLRequest::CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE);
        request->referrer = Referrer(GURL(request_->referrer()), blink::WebReferrerPolicyDefault);
    }
    request->fetch_type = fetch_type_;
    return request;
}

void ServiceWorkerURLRequestJob::CreateRequestBodyBlob(std::string* blob_uuid,
    uint64_t* blob_size)
{
    DCHECK(HasRequestBody());
    storage::BlobDataBuilder blob_builder(base::GenerateGUID());
    for (const ResourceRequestBodyImpl::Element& element : (*body_->elements())) {
        blob_builder.AppendIPCDataElement(element);
    }

    request_body_blob_data_handle_ = blob_storage_context_->AddFinishedBlob(&blob_builder);
    *blob_uuid = blob_builder.uuid();
    *blob_size = request_body_blob_data_handle_->size();
}

void ServiceWorkerURLRequestJob::DidPrepareFetchEvent(
    scoped_refptr<ServiceWorkerVersion> version)
{
    worker_ready_time_ = base::TimeTicks::Now();
    load_timing_info_.send_start = worker_ready_time_;

    // Record the time taken for the browser to find and possibly start an active
    // worker to which to dispatch a FetchEvent for a main frame resource request.
    // For context, a FetchEvent can only be dispatched to an ACTIVATED worker
    // that is running (it has been successfully started). The measurements starts
    // when the browser process receives the request. The browser then finds the
    // worker appropriate for this request (if there is none, this metric is not
    // recorded). If that worker is already started, the browser process can send
    // the request to it, so the measurement ends quickly. Otherwise the browser
    // process has to start the worker and the measurement ends when the worker is
    // successfully started.
    // The metric is not recorded in the following situations:
    // 1) The worker was in state INSTALLED or ACTIVATING, and the browser had to
    //    wait for it to become ACTIVATED. This is to avoid including the time to
    //    execute the activate event handlers in the worker's script.
    // 2) The worker was started for the fetch AND DevTools was attached during
    //    startup. This is intended to avoid including the time for debugging.
    // 3) The request is for New Tab Page. This is because it tends to dominate
    //    the stats and makes the results largely skewed.
    if (resource_type_ != RESOURCE_TYPE_MAIN_FRAME)
        return;
    if (!worker_already_activated_)
        return;
    if (version->skip_recording_startup_time() && initial_worker_status_ != EmbeddedWorkerStatus::RUNNING) {
        return;
    }
    if (version->should_exclude_from_uma())
        return;
    worker_start_situation_ = version->embedded_worker()->start_situation();
    ServiceWorkerMetrics::RecordActivatedWorkerPreparationForMainFrame(
        worker_ready_time_ - request()->creation_time(), initial_worker_status_,
        worker_start_situation_, did_navigation_preload_);
    MaybeReportNavigationPreloadMetrics();
}

void ServiceWorkerURLRequestJob::DidDispatchFetchEvent(
    ServiceWorkerStatusCode status,
    ServiceWorkerFetchEventResult fetch_result,
    const ServiceWorkerResponse& response,
    const scoped_refptr<ServiceWorkerVersion>& version)
{
    // Do not clear |fetch_dispatcher_| if it has dispatched a navigation preload
    // request to keep the mojom::URLLoader related objects in it, because the
    // preload response might still need to be streamed even after calling
    // respondWith().
    if (!did_navigation_preload_) {
        fetch_dispatcher_.reset();
    }
    ServiceWorkerMetrics::RecordFetchEventStatus(IsMainResourceLoad(), status);

    ServiceWorkerMetrics::URLRequestJobResult result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_BAD_DELEGATE;
    if (!delegate_->RequestStillValid(&result)) {
        RecordResult(result);
        DeliverErrorResponse();
        return;
    }

    if (status != SERVICE_WORKER_OK) {
        RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_FETCH_EVENT_DISPATCH);
        if (IsMainResourceLoad()) {
            // Using the service worker failed, so fallback to network.
            delegate_->MainResourceLoadFailed();
            FinalizeFallbackToNetwork();
        } else {
            DeliverErrorResponse();
        }
        return;
    }

    if (fetch_result == SERVICE_WORKER_FETCH_EVENT_RESULT_FALLBACK) {
        ServiceWorkerMetrics::RecordFallbackedRequestMode(request_mode_);
        if (IsFallbackToRendererNeeded()) {
            FinalizeFallbackToRenderer();
        } else {
            FinalizeFallbackToNetwork();
        }
        return;
    }

    // We should have a response now.
    DCHECK_EQ(SERVICE_WORKER_FETCH_EVENT_RESULT_RESPONSE, fetch_result);

    // A response with status code 0 is Blink telling us to respond with network
    // error.
    if (response.status_code == 0) {
        RecordStatusZeroResponseError(response.error);
        NotifyStartError(
            net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED));
        return;
    }

    load_timing_info_.send_end = base::TimeTicks::Now();

    // Creates a new HttpResponseInfo using the the ServiceWorker script's
    // HttpResponseInfo to show HTTPS padlock.
    // TODO(horo): When we support mixed-content (HTTP) no-cors requests from a
    // ServiceWorker, we have to check the security level of the responses.
    DCHECK(!http_response_info_);
    DCHECK(version);
    const net::HttpResponseInfo* main_script_http_info = version->GetMainScriptHttpResponseInfo();
    DCHECK(main_script_http_info);
    http_response_info_.reset(new net::HttpResponseInfo(*main_script_http_info));

    // Set up a request for reading the stream.
    if (response.stream_url.is_valid()) {
        DCHECK(response.blob_uuid.empty());
        SetResponseBodyType(STREAM);
        SetResponse(response);
        stream_reader_.reset(new ServiceWorkerStreamReader(this, version));
        stream_reader_->Start(response.stream_url);
        return;
    }

    // Set up a request for reading the blob.
    if (!response.blob_uuid.empty() && blob_storage_context_) {
        SetResponseBodyType(BLOB);
        std::unique_ptr<storage::BlobDataHandle> blob_data_handle = blob_storage_context_->GetBlobDataFromUUID(response.blob_uuid);
        if (!blob_data_handle) {
            // The renderer gave us a bad blob UUID.
            RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_BAD_BLOB);
            DeliverErrorResponse();
            return;
        }
        blob_reader_.reset(new ServiceWorkerBlobReader(this));
        blob_reader_->Start(std::move(blob_data_handle), request()->context());
    }

    SetResponse(response);
    if (!blob_reader_) {
        RecordResult(ServiceWorkerMetrics::REQUEST_JOB_HEADERS_ONLY_RESPONSE);
        CommitResponseHeader();
    }
}

void ServiceWorkerURLRequestJob::SetResponse(
    const ServiceWorkerResponse& response)
{
    response_url_list_ = response.url_list;
    service_worker_response_type_ = response.response_type;
    cors_exposed_header_names_ = response.cors_exposed_header_names;
    response_time_ = response.response_time;
    CreateResponseHeader(response.status_code, response.status_text,
        response.headers);
    load_timing_info_.receive_headers_end = base::TimeTicks::Now();

    response_is_in_cache_storage_ = response.is_in_cache_storage;
    response_cache_storage_cache_name_ = response.cache_storage_cache_name;
}

void ServiceWorkerURLRequestJob::CreateResponseHeader(
    int status_code,
    const std::string& status_text,
    const ServiceWorkerHeaderMap& headers)
{
    // TODO(kinuko): If the response has an identifier to on-disk cache entry,
    // pull response header from the disk.
    std::string status_line(
        base::StringPrintf("HTTP/1.1 %d %s", status_code, status_text.c_str()));
    status_line.push_back('\0');
    http_response_headers_ = new net::HttpResponseHeaders(status_line);
    for (ServiceWorkerHeaderMap::const_iterator it = headers.begin();
         it != headers.end();
         ++it) {
        std::string header;
        header.reserve(it->first.size() + 2 + it->second.size());
        header.append(it->first);
        header.append(": ");
        header.append(it->second);
        http_response_headers_->AddHeader(header);
    }
}

void ServiceWorkerURLRequestJob::CommitResponseHeader()
{
    if (!http_response_info_)
        http_response_info_.reset(new net::HttpResponseInfo());
    http_response_info_->headers.swap(http_response_headers_);
    http_response_info_->vary_data = net::HttpVaryData();
    http_response_info_->metadata = blob_reader_ ? blob_reader_->response_metadata() : nullptr;
    NotifyHeadersComplete();
}

void ServiceWorkerURLRequestJob::DeliverErrorResponse()
{
    // TODO(falken): Print an error to the console of the ServiceWorker and of
    // the requesting page.
    CreateResponseHeader(
        500, "Service Worker Response Error", ServiceWorkerHeaderMap());
    CommitResponseHeader();
}

void ServiceWorkerURLRequestJob::FinalizeFallbackToNetwork()
{
    // Restart this request to create a new job. The default job (which will hit
    // network) will be created in the next time because our request handler will
    // return nullptr after restarting and this means our interceptor does not
    // intercept.
    if (ShouldRecordResult())
        RecordResult(ServiceWorkerMetrics::REQUEST_JOB_FALLBACK_RESPONSE);
    response_type_ = FALLBACK_TO_NETWORK;
    NotifyRestartRequired();
    return;
}

void ServiceWorkerURLRequestJob::FinalizeFallbackToRenderer()
{
    // TODO(mek): http://crbug.com/604084 Figure out what to do about CORS
    // preflight and fallbacks for foreign fetch events.
    DCHECK_NE(fetch_type_, ServiceWorkerFetchType::FOREIGN_FETCH);
    fall_back_required_ = true;
    if (ShouldRecordResult())
        RecordResult(ServiceWorkerMetrics::REQUEST_JOB_FALLBACK_FOR_CORS);
    CreateResponseHeader(400, "Service Worker Fallback Required",
        ServiceWorkerHeaderMap());
    response_type_ = FALLBACK_TO_RENDERER;
    CommitResponseHeader();
}

bool ServiceWorkerURLRequestJob::IsFallbackToRendererNeeded() const
{
    // When the request_mode is |CORS| or |CORS-with-forced-preflight| and the
    // origin of the request URL is different from the security origin of the
    // document, we can't simply fallback to the network in the browser process.
    // It is because the CORS preflight logic is implemented in the renderer. So
    // we return a fall_back_required response to the renderer.
    // If fetch_type is |FOREIGN_FETCH| any required CORS checks will have already
    // been done in the renderer (and if a preflight was necesary the request
    // would never have reached foreign fetch), so such requests can always
    // fallback to the network directly.
    return !IsMainResourceLoad() && fetch_type_ != ServiceWorkerFetchType::FOREIGN_FETCH && (request_mode_ == FETCH_REQUEST_MODE_CORS || request_mode_ == FETCH_REQUEST_MODE_CORS_WITH_FORCED_PREFLIGHT) && (!request()->initiator().has_value() || !request()->initiator()->IsSameOriginWith(url::Origin(request()->url())));
}

void ServiceWorkerURLRequestJob::SetResponseBodyType(ResponseBodyType type)
{
    DCHECK_EQ(response_body_type_, UNKNOWN);
    DCHECK_NE(type, UNKNOWN);
    response_body_type_ = type;
}

bool ServiceWorkerURLRequestJob::ShouldRecordResult()
{
    return !did_record_result_ && is_started_ && response_type_ == FORWARD_TO_SERVICE_WORKER;
}

void ServiceWorkerURLRequestJob::RecordStatusZeroResponseError(
    blink::WebServiceWorkerResponseError error)
{
    // It violates style guidelines to handle a NOTREACHED() failure but if there
    // is a bug don't let it corrupt UMA results by double-counting.
    if (!ShouldRecordResult()) {
        NOTREACHED();
        return;
    }
    RecordResult(ServiceWorkerMetrics::REQUEST_JOB_ERROR_RESPONSE_STATUS_ZERO);
    ServiceWorkerMetrics::RecordStatusZeroResponseError(IsMainResourceLoad(),
        error);
}

void ServiceWorkerURLRequestJob::NotifyHeadersComplete()
{
    OnStartCompleted();
    URLRequestJob::NotifyHeadersComplete();
}

void ServiceWorkerURLRequestJob::NotifyStartError(
    net::URLRequestStatus status)
{
    OnStartCompleted();
    URLRequestJob::NotifyStartError(status);
}

void ServiceWorkerURLRequestJob::NotifyRestartRequired()
{
    ServiceWorkerResponseInfo::ForRequest(request_, true)
        ->OnPrepareToRestart(worker_start_time_, worker_ready_time_,
            did_navigation_preload_);
    delegate_->OnPrepareToRestart();
    URLRequestJob::NotifyRestartRequired();
}

void ServiceWorkerURLRequestJob::OnStartCompleted() const
{
    switch (response_type_) {
    case NOT_DETERMINED:
        NOTREACHED();
        return;
    case FAIL_DUE_TO_LOST_CONTROLLER:
    case FALLBACK_TO_NETWORK:
        // Indicate that the service worker did not respond to the request.
        ServiceWorkerResponseInfo::ForRequest(request_, true)
            ->OnStartCompleted(
                false /* was_fetched_via_service_worker */,
                false /* was_fetched_via_foreign_fetch */,
                false /* was_fallback_required */,
                std::vector<GURL>() /* url_list_via_service_worker */,
                blink::WebServiceWorkerResponseTypeDefault,
                base::TimeTicks() /* service_worker_start_time */,
                base::TimeTicks() /* service_worker_ready_time */,
                false /* response_is_in_cache_storage */,
                std::string() /* response_cache_storage_cache_name */,
                ServiceWorkerHeaderList() /* cors_exposed_header_names */,
                did_navigation_preload_);
        break;
    case FALLBACK_TO_RENDERER:
    case FORWARD_TO_SERVICE_WORKER:
        // Indicate that the service worker responded to the request, which is
        // considered true if "fallback to renderer" was required since the
        // renderer expects that.
        ServiceWorkerResponseInfo::ForRequest(request_, true)
            ->OnStartCompleted(
                true /* was_fetched_via_service_worker */,
                fetch_type_ == ServiceWorkerFetchType::FOREIGN_FETCH,
                fall_back_required_, response_url_list_,
                service_worker_response_type_, worker_start_time_,
                worker_ready_time_, response_is_in_cache_storage_,
                response_cache_storage_cache_name_, cors_exposed_header_names_,
                did_navigation_preload_);
        break;
    }
}

bool ServiceWorkerURLRequestJob::IsMainResourceLoad() const
{
    return ServiceWorkerUtils::IsMainResourceType(resource_type_);
}

bool ServiceWorkerURLRequestJob::HasRequestBody()
{
    // URLRequest::has_upload() must be checked since its upload data may have
    // been cleared while handling a redirect.
    return request_->has_upload() && body_.get() && blob_storage_context_;
}

void ServiceWorkerURLRequestJob::RequestBodyFileSizesResolved(bool success)
{
    file_size_resolver_.reset();
    if (!success) {
        RecordResult(
            ServiceWorkerMetrics::REQUEST_JOB_ERROR_REQUEST_BODY_BLOB_FAILED);
        // TODO(falken): This and below should probably be NotifyStartError, not
        // DeliverErrorResponse. But changing it causes
        // ServiceWorkerURLRequestJobTest.DeletedProviderHostBeforeFetchEvent to
        // fail.
        DeliverErrorResponse();
        return;
    }

    ServiceWorkerMetrics::URLRequestJobResult result = ServiceWorkerMetrics::REQUEST_JOB_ERROR_BAD_DELEGATE;
    ServiceWorkerVersion* active_worker = delegate_->GetServiceWorkerVersion(&result);
    if (!active_worker) {
        RecordResult(result);
        DeliverErrorResponse();
        return;
    }

    worker_already_activated_ = active_worker->status() == ServiceWorkerVersion::ACTIVATED;
    initial_worker_status_ = active_worker->running_status();

    DCHECK(!fetch_dispatcher_);
    fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher(
        CreateFetchRequest(), active_worker, resource_type_, timeout_,
        request()->net_log(),
        base::Bind(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent,
            weak_factory_.GetWeakPtr(), active_worker),
        base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
            weak_factory_.GetWeakPtr())));
    worker_start_time_ = base::TimeTicks::Now();
    did_navigation_preload_ = fetch_dispatcher_->MaybeStartNavigationPreload(
        request(),
        base::BindOnce(&ServiceWorkerURLRequestJob::OnNavigationPreloadResponse,
            weak_factory_.GetWeakPtr()));
    fetch_dispatcher_->Run();
}

void ServiceWorkerURLRequestJob::OnNavigationPreloadResponse()
{
    DCHECK(navigation_preload_response_time_.is_null());
    navigation_preload_response_time_ = base::TimeTicks::Now();
    MaybeReportNavigationPreloadMetrics();
}

void ServiceWorkerURLRequestJob::MaybeReportNavigationPreloadMetrics()
{
    if (worker_start_time_.is_null() || worker_ready_time_.is_null() || navigation_preload_response_time_.is_null()) {
        return;
    }
    DCHECK(!reported_navigation_preload_metrics_);
    reported_navigation_preload_metrics_ = true;

    ServiceWorkerMetrics::RecordNavigationPreloadResponse(
        worker_ready_time_ - worker_start_time_,
        navigation_preload_response_time_ - worker_start_time_,
        initial_worker_status_, worker_start_situation_);
}

} // namespace content
